{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### This jupyter notebooks provides the code for classifying signals using the Discrete Wavelet Transform.\n",
    "### To get some more background information, please have a look at the accompanying blog-post:\n",
    "### http://ataspinar.com/2018/12/21/a-guide-for-using-the-wavelet-transform-in-machine-learning/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import time\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import scipy.io as sio\n",
    "from IPython.display import display\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import pywt\n",
    "import scipy.stats\n",
    "\n",
    "import datetime as dt\n",
    "from collections import defaultdict, Counter\n",
    "\n",
    "from sklearn.ensemble import GradientBoostingClassifier\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.tree import DecisionTreeClassifier\n",
    "from sklearn.svm import SVC\n",
    "from sklearn.neural_network import MLPClassifier\n",
    "from sklearn.naive_bayes import GaussianNB\n",
    "from sklearn.ensemble import AdaBoostClassifier\n",
    "from sklearn.gaussian_process import GaussianProcessClassifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "dict_classifiers = {\n",
    "    \"Gradient Boosting Classifier\": GradientBoostingClassifier(),\n",
    "    \"Random Forest\": RandomForestClassifier(),\n",
    "    \"Logistic Regression\": LogisticRegression(),\n",
    "    \"Nearest Neighbors\": KNeighborsClassifier(),\n",
    "    \"Decision Tree\": DecisionTreeClassifier(),\n",
    "    \"Linear SVM\": SVC(),\n",
    "    \"Neural Net\": MLPClassifier(alpha = 1),\n",
    "    \"Naive Bayes\": GaussianNB(),   \n",
    "    \"AdaBoost\": AdaBoostClassifier(),\n",
    "    \"Gaussian Process\": GaussianProcessClassifier()\n",
    "}\n",
    "   \n",
    "def batch_classify(X_train, Y_train, X_test, Y_test, no_classifiers = 5, verbose = True):\n",
    "    \"\"\"\n",
    "    This method, takes as input the X, Y matrices of the Train and Test set.\n",
    "    And fits them on all of the Classifiers specified in the dict_classifier.\n",
    "    Usually, the SVM, Random Forest and Gradient Boosting Classifier take quiet some time to train. \n",
    "    So it is best to train them on a smaller dataset first and \n",
    "    decide whether you want to comment them out or not based on the test accuracy score.\n",
    "    \"\"\"\n",
    "    \n",
    "    dict_models = {}\n",
    "    for classifier_name, classifier in list(dict_classifiers.items())[:no_classifiers]:\n",
    "        t_start = time.clock()\n",
    "        classifier.fit(X_train, Y_train)\n",
    "        t_end = time.clock()\n",
    "        \n",
    "        t_diff = t_end - t_start\n",
    "        train_score = classifier.score(X_train, Y_train)\n",
    "        test_score = classifier.score(X_test, Y_test)\n",
    "        \n",
    "        dict_models[classifier_name] = {'model': classifier, 'train_score': train_score, 'test_score': test_score, 'train_time': t_diff}\n",
    "        if verbose:\n",
    "            print(\"trained {c} in {f:.2f} s\".format(c=classifier_name, f=t_diff))\n",
    "    return dict_models\n",
    "\n",
    "def get_train_test(df, y_col, x_cols, ratio):\n",
    "    \"\"\" \n",
    "    This method transforms a dataframe into a train and test set, for this you need to specify:\n",
    "    1. the ratio train : test (usually 0.7)\n",
    "    2. the column with the Y_values\n",
    "    \"\"\"\n",
    "    mask = np.random.rand(len(df)) < ratio\n",
    "    df_train = df[mask]\n",
    "    df_test = df[~mask]\n",
    "       \n",
    "    Y_train = df_train[y_col].values\n",
    "    Y_test = df_test[y_col].values\n",
    "    X_train = df_train[x_cols].values\n",
    "    X_test = df_test[x_cols].values\n",
    "    return df_train, df_test, X_train, Y_train, X_test, Y_test\n",
    "    \n",
    "def display_dict_models(dict_models, sort_by='test_score'):\n",
    "    cls = [key for key in dict_models.keys()]\n",
    "    test_s = [dict_models[key]['test_score'] for key in cls]\n",
    "    training_s = [dict_models[key]['train_score'] for key in cls]\n",
    "    training_t = [dict_models[key]['train_time'] for key in cls]\n",
    "    \n",
    "    df_ = pd.DataFrame(data=np.zeros(shape=(len(cls),4)), columns = ['classifier', 'train_score', 'test_score', 'train_time'])\n",
    "    for ii in range(0,len(cls)):\n",
    "        df_.loc[ii, 'classifier'] = cls[ii]\n",
    "        df_.loc[ii, 'train_score'] = training_s[ii]\n",
    "        df_.loc[ii, 'test_score'] = test_s[ii]\n",
    "        df_.loc[ii, 'train_time'] = training_t[ii]\n",
    "    \n",
    "    display(df_.sort_values(by=sort_by, ascending=False))\n",
    "    \n",
    "def calculate_entropy(list_values):\n",
    "    counter_values = Counter(list_values).most_common()\n",
    "    probabilities = [elem[1]/len(list_values) for elem in counter_values]\n",
    "    entropy=scipy.stats.entropy(probabilities)\n",
    "    return entropy\n",
    "\n",
    "def calculate_statistics(list_values):\n",
    "    n5 = np.nanpercentile(list_values, 5)\n",
    "    n25 = np.nanpercentile(list_values, 25)\n",
    "    n75 = np.nanpercentile(list_values, 75)\n",
    "    n95 = np.nanpercentile(list_values, 95)\n",
    "    median = np.nanpercentile(list_values, 50)\n",
    "    mean = np.nanmean(list_values)\n",
    "    std = np.nanstd(list_values)\n",
    "    var = np.nanvar(list_values)\n",
    "    rms = np.nanmean(np.sqrt(list_values**2))\n",
    "    return [n5, n25, n75, n95, median, mean, std, var, rms]\n",
    "\n",
    "def calculate_crossings(list_values):\n",
    "    zero_crossing_indices = np.nonzero(np.diff(np.array(list_values) > 0))[0]\n",
    "    no_zero_crossings = len(zero_crossing_indices)\n",
    "    mean_crossing_indices = np.nonzero(np.diff(np.array(list_values) > np.nanmean(list_values)))[0]\n",
    "    no_mean_crossings = len(mean_crossing_indices)\n",
    "    return [no_zero_crossings, no_mean_crossings]\n",
    "\n",
    "def get_features(list_values):\n",
    "    entropy = calculate_entropy(list_values)\n",
    "    crossings = calculate_crossings(list_values)\n",
    "    statistics = calculate_statistics(list_values)\n",
    "    return [entropy] + crossings + statistics\n",
    "\n",
    "def get_uci_har_features(dataset, labels, waveletname):\n",
    "    uci_har_features = []\n",
    "    for signal_no in range(0, len(dataset)):\n",
    "        features = []\n",
    "        for signal_comp in range(0,dataset.shape[2]):\n",
    "            signal = dataset[signal_no, :, signal_comp]\n",
    "            list_coeff = pywt.wavedec(signal, waveletname)\n",
    "            for coeff in list_coeff:\n",
    "                features += get_features(coeff)\n",
    "        uci_har_features.append(features)\n",
    "    X = np.array(uci_har_features)\n",
    "    Y = np.array(labels)\n",
    "    return X, Y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1. Loading the UCI HAR dataset\n",
    "download dataset from https://archive.ics.uci.edu/ml/datasets/Human+Activity+Recognition+Using+Smartphones"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The train dataset contains 7352 signals, each one of length 128 and 9 components \n",
      "The test dataset contains 7352 signals, each one of length 128 and 9 components \n",
      "The train dataset contains 7352 labels, with the following distribution:\n",
      " Counter({6: 1407, 5: 1374, 4: 1286, 1: 1226, 2: 1073, 3: 986})\n",
      "The test dataset contains 2947 labels, with the following distribution:\n",
      " Counter({6: 537, 5: 532, 1: 496, 4: 491, 2: 471, 3: 420})\n"
     ]
    }
   ],
   "source": [
    "activities_description = {\n",
    "    1: 'walking',\n",
    "    2: 'walking upstairs',\n",
    "    3: 'walking downstairs',\n",
    "    4: 'sitting',\n",
    "    5: 'standing',\n",
    "    6: 'laying'\n",
    "}\n",
    "\n",
    "def read_signals(filename):\n",
    "    with open(filename, 'r') as fp:\n",
    "        data = fp.read().splitlines()\n",
    "        data = map(lambda x: x.rstrip().lstrip().split(), data)\n",
    "        data = [list(map(float, line)) for line in data]\n",
    "    return data\n",
    "\n",
    "def read_labels(filename):        \n",
    "    with open(filename, 'r') as fp:\n",
    "        activities = fp.read().splitlines()\n",
    "        activities = list(map(int, activities))\n",
    "    return activities\n",
    "\n",
    "def randomize(dataset, labels):\n",
    "    permutation = np.random.permutation(labels.shape[0])\n",
    "    shuffled_dataset = dataset[permutation, :, :]\n",
    "    shuffled_labels = labels[permutation]\n",
    "    return shuffled_dataset, shuffled_labels\n",
    "\n",
    "INPUT_FOLDER_TRAIN = './data/UCI_HAR/train/InertialSignals/'\n",
    "INPUT_FOLDER_TEST = './data/UCI_HAR/test/InertialSignals/'\n",
    "\n",
    "INPUT_FILES_TRAIN = ['body_acc_x_train.txt', 'body_acc_y_train.txt', 'body_acc_z_train.txt', \n",
    "                     'body_gyro_x_train.txt', 'body_gyro_y_train.txt', 'body_gyro_z_train.txt',\n",
    "                     'total_acc_x_train.txt', 'total_acc_y_train.txt', 'total_acc_z_train.txt']\n",
    "\n",
    "INPUT_FILES_TEST = ['body_acc_x_test.txt', 'body_acc_y_test.txt', 'body_acc_z_test.txt', \n",
    "                     'body_gyro_x_test.txt', 'body_gyro_y_test.txt', 'body_gyro_z_test.txt',\n",
    "                     'total_acc_x_test.txt', 'total_acc_y_test.txt', 'total_acc_z_test.txt']\n",
    "\n",
    "LABELFILE_TRAIN = './data/UCI_HAR/train/y_train.txt'\n",
    "LABELFILE_TEST = './data/UCI_HAR/test/y_test.txt'\n",
    "\n",
    "train_signals, test_signals = [], []\n",
    "\n",
    "for input_file in INPUT_FILES_TRAIN:\n",
    "    signal = read_signals(INPUT_FOLDER_TRAIN + input_file)\n",
    "    train_signals.append(signal)\n",
    "train_signals = np.transpose(np.array(train_signals), (1, 2, 0))\n",
    "\n",
    "for input_file in INPUT_FILES_TEST:\n",
    "    signal = read_signals(INPUT_FOLDER_TEST + input_file)\n",
    "    test_signals.append(signal)\n",
    "test_signals = np.transpose(np.array(test_signals), (1, 2, 0))\n",
    "\n",
    "train_labels = read_labels(LABELFILE_TRAIN)\n",
    "test_labels = read_labels(LABELFILE_TEST)\n",
    "\n",
    "[no_signals_train, no_steps_train, no_components_train] = np.shape(train_signals)\n",
    "[no_signals_test, no_steps_test, no_components_test] = np.shape(train_signals)\n",
    "no_labels = len(np.unique(train_labels[:]))\n",
    "\n",
    "print(\"The train dataset contains {} signals, each one of length {} and {} components \".format(no_signals_train, no_steps_train, no_components_train))\n",
    "print(\"The test dataset contains {} signals, each one of length {} and {} components \".format(no_signals_test, no_steps_test, no_components_test))\n",
    "print(\"The train dataset contains {} labels, with the following distribution:\\n {}\".format(np.shape(train_labels)[0], Counter(train_labels[:])))\n",
    "print(\"The test dataset contains {} labels, with the following distribution:\\n {}\".format(np.shape(test_labels)[0], Counter(test_labels[:])))\n",
    "\n",
    "uci_har_signals_train, uci_har_labels_train = randomize(train_signals, np.array(train_labels))\n",
    "uci_har_signals_test, uci_har_labels_test = randomize(test_signals, np.array(test_labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. Generating features for the UCI-HAR features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "waveletname = 'rbio3.1'\n",
    "X_train, Y_train = get_uci_har_features(uci_har_signals_train, uci_har_labels_train, waveletname)\n",
    "X_test, Y_test = get_uci_har_features(uci_har_signals_test, uci_har_labels_test, waveletname)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3. Classifying the train and test sets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "trained Gradient Boosting Classifier in 398.44 s\n",
      "trained Random Forest in 1.47 s\n",
      "trained Logistic Regression in 22.68 s\n",
      "trained Nearest Neighbors in 0.63 s\n",
      "trained Decision Tree in 7.32 s\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>classifier</th>\n",
       "      <th>train_score</th>\n",
       "      <th>test_score</th>\n",
       "      <th>train_time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Gradient Boosting Classifier</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.949101</td>\n",
       "      <td>398.442859</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Logistic Regression</td>\n",
       "      <td>0.980277</td>\n",
       "      <td>0.932134</td>\n",
       "      <td>22.677510</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Random Forest</td>\n",
       "      <td>0.999320</td>\n",
       "      <td>0.900916</td>\n",
       "      <td>1.468736</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Decision Tree</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.850017</td>\n",
       "      <td>7.320323</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Nearest Neighbors</td>\n",
       "      <td>0.954570</td>\n",
       "      <td>0.838480</td>\n",
       "      <td>0.626256</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                     classifier  train_score  test_score  train_time\n",
       "0  Gradient Boosting Classifier     1.000000    0.949101  398.442859\n",
       "2           Logistic Regression     0.980277    0.932134   22.677510\n",
       "1                 Random Forest     0.999320    0.900916    1.468736\n",
       "4                 Decision Tree     1.000000    0.850017    7.320323\n",
       "3             Nearest Neighbors     0.954570    0.838480    0.626256"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "models = batch_classify(X_train, Y_train, X_test, Y_test)\n",
    "display_dict_models(models)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
